#include "HardwareBufferManager.h"
#include "VertexIndexData.h"
#include "LogManager.h"

namespace PVM
{
	template<> HardwareBufferManager* Singleton<HardwareBufferManager>::ms_Singleton = 0;
	HardwareBufferManager* HardwareBufferManager::getSingletonPtr(void)
	{
		return ms_Singleton;
	}
	HardwareBufferManager& HardwareBufferManager::getSingleton(void)
	{
		assert(ms_Singleton); return (*ms_Singleton);
	}
	HardwareBufferManager::HardwareBufferManager(HardwareBufferManagerBase* imp)
	: HardwareBufferManagerBase(), mImpl(imp)
	{
	}
	
	HardwareBufferManager::~HardwareBufferManager()
	{
	}
	
	const size_t HardwareBufferManagerBase::UNDER_USED_FRAME_THRESHOLD = 30000;
	const size_t HardwareBufferManagerBase::EXPIRED_DELAY_FRAME_THRESHOLD = 5;
	
	HardwareBufferManagerBase::HardwareBufferManagerBase()
	: mUnderUsedFrameCount(0)
	{
	}
	
	HardwareBufferManagerBase::~HardwareBufferManagerBase()
	{
		mVertexBuffers.clear();
		mIndexBuffers.clear();
		
		destroyAllDeclarations();
		destroyAllBindings();
	}
	
	VertexDeclaration* HardwareBufferManagerBase::createVertexDeclaration(void)
	{
		VertexDeclaration* decl = createVertexDeclarationImpl();
		LOCK_MUTEX(mVertexDeclarationsMutex);
		mVertexDeclarations.insert(decl);
		return decl;
	}
	
	void HardwareBufferManagerBase::destroyVertexDeclaration(VertexDeclaration* decl)
	{
		LOCK_MUTEX(mVertexDeclarationsMutex);
		mVertexDeclarations.erase(decl);
		destroyVertexDeclarationImpl(decl);
	}
	
	VertexBufferBinding* HardwareBufferManagerBase::createVertexBufferBinding(void)
	{
		VertexBufferBinding* ret = createVertexBufferBindingImpl();
		LOCK_MUTEX(mVertexBufferBindingsMutex);
		mVertexBufferBindings.insert(ret);
		return ret;
	}
	
	void HardwareBufferManagerBase::destroyVertexBufferBinding(VertexBufferBinding* binding)
	{
		LOCK_MUTEX(mVertexBufferBindingsMutex);
		mVertexBufferBindings.erase(binding);
		destroyVertexBufferBindingImpl(binding);
	}
	
	VertexDeclaration* HardwareBufferManagerBase::createVertexDeclarationImpl(void)
	{
		return new VertexDeclaration();
	}
	
	void HardwareBufferManagerBase::destroyVertexDeclarationImpl(VertexDeclaration* decl)
	{
		delete decl;
	}
	
	VertexBufferBinding* HardwareBufferManagerBase::createVertexBufferBindingImpl(void)
	{
		return new VertexBufferBinding();
	}
	
	void HardwareBufferManagerBase::destroyVertexBufferBindingImpl(VertexBufferBinding* binding)
	{
		delete binding;
	}
	
	void HardwareBufferManagerBase::destroyAllDeclarations(void)
	{
		LOCK_MUTEX(mVertexDeclarationsMutex);
		VertexDeclarationList::iterator decl;
		for (decl = mVertexDeclarations.begin(); decl != mVertexDeclarations.end(); ++decl)
		{
			destroyVertexDeclarationImpl(*decl);
		}
		mVertexDeclarations.clear();
	}
	
	void HardwareBufferManagerBase::destroyAllBindings(void)
	{
		LOCK_MUTEX(mVertexBufferBindingsMutex);
		VertexBufferBindingList::iterator bind;
		for (bind = mVertexBufferBindings.begin(); bind != mVertexBufferBindings.end(); ++bind)
		{
			destroyVertexBufferBindingImpl(*bind);
		}
		mVertexBufferBindings.clear();
	}
	
	void HardwareBufferManagerBase::registerVertexBufferSourceAndCopy(
	const HardwareVertexBufferSharedPtr& sourceBuffer,
	const HardwareVertexBufferSharedPtr& copy)
	{
		LOCK_MUTEX(mTempBuffersMutex);
		mFreeTempVertexBufferMap.insert(
			FreeTemporaryVertexBufferMap::value_type(sourceBuffer.get(), copy)
		);
	}
	
	HardwareVertexBufferSharedPtr
	HardwareBufferManagerBase::allocateVertexBufferCopy(
	const HardwareVertexBufferSharedPtr& sourceBuffer,
	BufferLicenseType licenseType, HardwareBufferLicensee* licensee,
														bool copyData)
	{
		LOCK_MUTEX(mVertexBuffersMutex);
		{
			LOCK_MUTEX(mTempBuffersMutex);
			
			FreeTemporaryVertexBufferMap::iterator i =
			mFreeTempVertexBufferMap.find(sourceBuffer.get());
			if (i == mFreeTempVertexBufferMap.end())
			{
				vbuf = makeBufferCopy(
				sourceBuffer,
									  HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY_DISCARDABLE,
									  true);
			}
			else
			{
				vbuf = i->second;
				mFreeTempVertexBufferMap.erase(i);
			}
			
			if (copyData)
			{
				vbuf->copyData(*(sourceBuffer.get()), 0, 0, sourceBuffer->getSizeInBytes(), true);
			}
			
			mTempVertexBufferLicenses.insert(
											 TemporaryVertexBufferLicenseMap::value_type(
											 vbuf.get(),
											 VertexBufferLicense(sourceBuffer.get(), licenseType, EXPIRED_DELAY_FRAME_THRESHOLD, vbuf, licensee)
											 )
			);
			return vbuf;
		}
	}
	
	void HardwareBufferManagerBase::releaseVertexBufferCopy(
	const HardwareVertexBufferSharedPtr& bufferCopy)
	{
		LOCK_MUTEX(mTempBuffersMutex);
		
		TemporaryVertexBufferLicenseMap::iterator i =
		mTempVertexBufferLicenses.find(bufferCopy.get());
		if (i != mTempVertexBufferLicenses.end())
		{
			const VertexBufferLicense& vbl = i->second;
			vbl.licensee->licenseExpired(vbl.buffer.get());
			mFreeTempVertexBufferMap.insert(
											FreeTemporaryVertexBufferMap::value_type(vbl.originalBufferPtr, vbl.buffer));
			mTempVertexBufferLicenses.erase(i);
		}
	}
	
	void HardwareBufferManagerBase::touchVertexBufferCopy(
	const HardwareVertexBufferSharedPtr& bufferCopy)
	{
		LOCK_MUTEX(mTempBuffersMutex);
		TemporaryVertexBufferLicenseMap::iterator i = 
		mTempVertexBufferLicenses.find(bufferCopy.get());
		if (i != mTempVertexBufferLicenses.end())
		{
			VertexBufferLicense& vbl = i->second;
			assert(vbl.licenseType == BLT_AUTOMATIC_RELEASE);
			
			vbl.expiredDelay = EXPIRED_DELAY_FRAME_THRESHOLD;
		}
	}
	
	void HardwareBufferManagerBase::_freeUnusedBufferCopies(void)
	{
		LOCK_MUTEX(mTempBuffersMutex);
		size_t numFreed = 0;
		
		FreeTemporaryVertexBufferMap::iterator i;
		i = mFreeTempVertexBufferMap.begin();
		while (i != mFreeTempVertexBufferMap.end())
		{
			FreeTemporaryVertexBufferMap::iterator icur = i++;
			
			if (icur->second.useCount() <= 1)
			{
				++numFreed;
				mFreeTempVertexBufferMap.erase(icur);
			}
		}
		
		StringUtil::StrStreamType str;
		if (numFreed)
		{
			str << "HardwareBufferManager: Freed " << numFreed << " unused temporary vertex buffers.";
		}
		else
		{
			str << "HardwareBufferManager: No unused temporary vertex buffers found.";
		}
		LogManager::getSingleton().logMessage(str.str(), LML_TRIVIAL);
	}
	
	void HardwareBufferManagerBase::_releaseBufferCopies(bool forceFreeUnused)
	{
		LOCK_MUTEX(mTempBuffersMutex);
		size_t numUnused = mFreeTempVertexBufferMap.size();
		size_t numUsed = mTempVertexBufferLicenses.size();
		
		TemporaryVertexBufferLicenseMap::iterator i;
		i = mTempVertexBufferLicenses.begin();
		while (i != mTempVertexBufferLicenses.end())
		{
			TemporaryVertexBufferLicenseMap::iterator icur = i++;
			VertexBufferLicense& vbl = icur->second;
			if (vbl.licenseType == BLT_AUTOMATIC_RELEASE &&
				(forceFreeUnused || --vbl.expiredDelay <= 0))
			{
				vbl.license->licenseExpired(vbl.buffer.get());
				
				mFreeTempVertexBufferMap.insert(
												FreeTemporaryVertexBufferMap::value_type(vbl.originalBufferPtr, vbl.buffer));
				mTempVertexBufferLicenses.erase(icur);
			}
		}
		
		if (forceFreeUnused)
		{
			_freeUnusedBufferCopies();
			mUnderUsedFrameCount = 0;
		}
		else 
		{
			if (numUsed < numUnused)
			{
				++mUnderUsedFrameCount;
				if (mUnderUsedFrameCount >= UNDER_USED_FRAME_THRESHOLD)
				{
					_freeUnusedBufferCopies();
					mUnderUsedFrameCount = 0;
				}
			}
			else
			{
				mUnderUsedFrameCount = 0;
			}
		}
	}
	
	void HardwareBufferManagerBase::_forceReleaseBufferCopies(
		const HardwareVertexBufferSharedPtr& sourceBuffer)
	{
		_forceReleaseBufferCopies(sourceBuffer.get());
	}
	
	void HardwareBufferManagerBase::_forceReleaseBufferCopies(
		HardwareVertexBuffer* sourceBuffer)
	{
		LOCK_MUTEX(mTempBuffersMutex);
		TemporaryVertexBufferLicenseMap::iterator i;
		i = mTempVertexBufferLicenses.begin();
		while (i != mTempVertexBufferLicenses.end())
		{
			TemporaryVertexBufferLicenseMap::iterator icur = i++;
			const VertexBufferLicense& vbl = icur->second;
			if (vbl.originalBufferPtr == sourceBuffer)
			{
				vbl.licensee->licenseExpired(vbl.buffer.get());
				mTempVertexBufferLicenses.erase(icur);
			}
		}
		
		typedef FreeTemporaryVertexBufferMap::iterator _Iter;
		std::pair<_Iter, _Iter> range = mFreeTempVertexBufferMap.equal_range(sourceBuffer);
		if (range.first != range.second)
		{
			list<HardwareVertexBufferSharedPtr>::type holdForDelayDestroy;
			for (_Iter it = range.first; it != range.second; ++it)
			{
				if (it->second.useCount() <= 1)
				{
					holdForDelayDestroy.push_back(it->second);
				}
			}
			
			mFreeTempVertexBufferMap.erase(range.first, range.second);
		}
	}
	
	void HardwareBufferManagerBase::_notifyVertexBufferDestroyed(HardwareVertexBuffer* buf)
	{
		LOCK_MUTEX(mVertexBuffersMutex);
		
		VertexBufferList::iterator i = mVertexBuffers.find(buf);
		if (i != mVertexBuffers.end())
		{
			mVertexBuffers.erase(i);
			_forceReleaseBufferCopies(buf);
		}
	}
	
	void HardwareBufferManagerBase::_notifyIndexBufferDestroyed(HardwareIndexBuffer* buf)
	{
		LOCK_MUTEX(mIndexBuffersMutex);
		
		IndexBufferList::iterator i = mIndexBuffers.find(buf);
		if (i != mIndexBuffers.end())
		{
			mIndexBuffers.erase(i);
		}
	}
	
	HardwareVertexBufferSharedPtr
	HardwareBufferManagerBase::makeBufferCopy(
	const HardwareVertexBufferSharedPtr& source,
			HardwareBuffer::Usage usage, bool useShadowBuffer)
	{
		return this->createVertexBuffer(
		source->getVertexSize(),
		source->getNumVertices(),
		usage, useShadowBuffer);
	}
	
	TempBlendedBufferInfo::~TempBlendedBufferInfo(void)
	{
		if (!destPositionBuffer.isNull))
			destPositionBuffer->getManager()->releaseVertexBufferCopy(destPositionBuffer);
		if (!destNormalBuffer.isNull())
			destNormalBuffer->getManager()->releaseVertexBufferCopy(destNormalBuffer);
	}
	
	void TempBlendedBufferInfo::extractFrom(const VertexData* sourceData)
	{
		if (!destPositionBuffer.isNull())
		{
			destPositionBuffer->getManager()->releaseVertexBufferCopy(destPositionBuffer);
			assert(destPositionBuffer.isNull());
		}
		if (!destNormalBuffer.isNull())
		{
			destNormalBuffer->getManager()->releaseVertexBufferCopy(destNormalBuffer);
			assert(destNormalBuffer.isNull());
		}
		VertexDeclaration* decl = sourceData->vertexDeclaration;
		VertexBufferBinding* bind = sourceData->vertexBufferBinding;
		const VertexElement* posElem = decl->findElementBySemantic(VES_POSITION);
		const VertexElement* normElem = decl->findElementBySemantic(VES_NORMAL);
		
		assert(posElem && "Positons are required");
		
		posBindIndex = posElem->getSource();
		srcPositionBuffer = bind->getBuffer(posBindIndex);
		
		if (!normElem)
		{
			posNormalSharedBuffer = false;
			srcNormalBuffer.setNull();
		}
		else
		{
			normBindIndex = normElem->getSource();
			if (normBindIndex == posBindIndex)
			{
				posNormalSharedBuffer = true;
				srcNormalBuffer.setNull();
			}
			else
			{
				posNormalSharedBuffer = false;
				srcNormalBuffer->bind->getBuffer(normBindIndex);
			}
		}
	}
	
	void TempBlendedBufferInfo::checkoutTempCopies(bool positions, bool normals)
	{
		bindPositions = positions;
		bindNormals = normals;
		
		if (positions && destPositionBuffer.isNull())
		{
			destPositionBuffer = srcPositionBuffer->getManager()->allocateVertexBufferCopy(srcPositionBuffer,
																						   HardwareBufferManager::BLT_AUTOMATIC_RELEASE, this);
		}
		if (normals && !posNormalSharedBuffer && !srcNormalBuffer.isNull() && destNormalBuffer.isNull())
		{
			destNormalBuffer = srcNormalBuffer->getManager()->allocateVertexBufferCopy(srcNormalBuffer,
																					   HardwareBufferManagerBase::BLT_AUTOMATIC_RELEASE, this);
		}
	}
	
	bool TempBlendedBufferInfo::buffersCheckedOut(bool positions, bool normals) const
	{
		if (positions || (normals && posNormalSharedBuffer))
		{
			if (destPositionBuffer.isNull())
				return false;
			
			destPositionBuffer->getManager()->touchVertexBufferCopy(destPositionBuffer);
		}
		
		if (normals && !posNormalSharedBuffer)
		{
			if (destNormalBuffer.isNull())
				return false;
			
			destNormalBuffer->getManager()->touchVertexBufferCopy(destNormalBuffer);
		}
		
		return true;
	}
	
	void TempBlendedBufferInfo::bindTempCopies(Vertex* targetData, bool suppressHardwareUpload)
	{
		this->destPositionBuffer->suppressHardwareUpdate(suppressHardwareUpload);
		targetData->vertexBufferBinding->setBinding(
			this->posBindIndex, this->destPositionBuffer
		);
		if (bindNormals && !posNormalSharedBuffer && !destNormalBuffer.isNull())
		{
			this->destNormalBuffer->suppressHardwareUpdate(suppressHardwareUpload);
			targetData->vertexBufferBinding->setBinding(
				this->normBindIndex, this->destNormalBuffer);
		}
	}
	
	void TempBlendedBufferInfo::licenseExpired(HardwareBuffer* buffer)
	{
		assert(buffer == destPositionBuffer.get()
			   || buffer == destNormalBuffer.get());
		if (buffer == destPositionBuffer.get())
			destPositionBuffer.setNull();
		if (buffer == destNormalBuffer.get())
			destNormalBuffer.setNull();
	}
}
